﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HidLibrary;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles; 

namespace ScpControl
{
    public struct ledColor
    {
        public byte red;
        public byte green;
        public byte blue;
    }

    public class DS4Device
    {
        private short charge = 0;
        private bool isUSB = true;
        private int deviceNum = 0;
        private bool m_isTouchEnabled = false;
        private bool lastIsActive = false;
        private byte rumbleBoost = 100;
        private byte smallRumble = 0;
        private byte bigRumble = 0;
        private byte ledFlash = 0;
        private HidDevice hid_device;
        private ledColor m_LedColor;

        public HidDevice Device
        {
            get { return hid_device; }
        }

        public bool Gone
        {
            get
            {
                return hid_device == null;
            }
            set
            {
                hid_device = null;
            }
        }

        public byte SmallRumble
        {
            get {
                uint boosted = ((uint)smallRumble * (uint)rumbleBoost)/100;
                if (boosted > 255)
                    boosted = 255;
                return (byte)boosted; 
            }
            set { smallRumble = value; }
        }

        public byte BigRumble
        {
            get
            {
                uint boosted = ((uint)bigRumble * (uint)rumbleBoost) / 100;
                if (boosted > 255)
                    boosted = 255;
                return (byte)boosted;
            }
            set { bigRumble = value; }
        }

        public byte RumbleBoost
        {
            get { return rumbleBoost; }
            set { rumbleBoost = value; }
        }

        public bool isTouchEnabled
        {
            get { return m_isTouchEnabled; }
            set { m_isTouchEnabled = value; }
        }

        public void setLedColor(byte red, byte green, byte blue)
        {
            m_LedColor = new ledColor();
            m_LedColor.red = red;
            m_LedColor.green = green;
            m_LedColor.blue = blue;
        }

        public ledColor LedColor
        {
            get { return m_LedColor; }
            set { m_LedColor = value; }
        }

        public byte FlashLed
        {
            get { return ledFlash; }
            set { ledFlash = value; }
        }

        private byte[] data, data64; // data64 used for Bluetooth
        public DS4Device(HidDevice device, int controllerID)
        {
            hid_device = device;
            deviceNum = controllerID;
            data = new byte[Device.Capabilities.InputReportByteLength];
            isUSB = Device.Capabilities.InputReportByteLength == 64;
            if (isUSB) {
                outputData = new byte[Device.Capabilities.OutputReportByteLength];
            } else {
                outputData = new byte[78];
                data64 = new byte[Device.Capabilities.InputReportByteLength];
            }
            isTouchEnabled = Global.getTouchEnabled(deviceNum);
        }

        public SafeFileHandle FileHandle
        {
            get { return Device.FileHandle; }
        }

        public byte[] retrieveData(BusDevice bd)
        {
            if (!isUSB)
            {
                if (Device.ReadWithFileStream(data) == HidDevice.ReadStatus.Success)
                {
                    Array.Copy(data, 2, data64, 0, 64);
                    bool touchPressed = (data64[7] & (1 << 2 - 1)) != 0;
                    updateBatteryStatus(data64[30], isUSB);
                    if (isTouchEnabled && bd != null)
                        handleTouchpad(data64, touchPressed, bd);
                }
                else
                {
                    return null;
                }
                if (Global.getFlushHIDQueues())
                    Device.flush_Queue();
                return mapButtons(data64);
            }
            else
            {
                if (Device.ReadWithFileStream(data) == HidDevice.ReadStatus.Success)
                {
                    bool touchPressed = (data[7] & (1 << 2 - 1)) != 0;
                    updateBatteryStatus(data[30], isUSB);
                    if (isTouchEnabled && bd != null)
                        handleTouchpad(data, touchPressed, bd);
                }
                else
                {
                    return null;
                }
                if (Global.getFlushHIDQueues())
                    Device.flush_Queue();
                return mapButtons(data);
            }
        }

        private byte[] Report = new byte[64];
        private byte[] mapButtons(byte[] data)
        {
            Report[1] = 0x02;
            Report[2] = 0x05;
            Report[3] = 0x12;

            Report[14] = data[1]; //Left Stick X


            Report[15] = data[2]; //Left Stick Y


            Report[16] = data[3]; //Right Stick X


            Report[17] = data[4]; //Right Stick Y


            Report[26] = data[8]; //Left Trrigger


            Report[27] = data[9]; //Right Trigger

            var bitY = ((byte)data[5] & (1 << 8 - 1)) != 0;
            var bitB = ((byte)data[5] & (1 << 7 - 1)) != 0;
            var bitA = ((byte)data[5] & (1 << 6 - 1)) != 0;
            var bitX = ((byte)data[5] & (1 << 5 - 1)) != 0;
            var dpadUpBit = ((byte)data[5] & (1 << 4 - 1)) != 0;
            var dpadDownBit = ((byte)data[5] & (1 << 3 - 1)) != 0;
            var dpadLeftBit = ((byte)data[5] & (1 << 2 - 1)) != 0;
            var dpadRightBit = ((byte)data[5] & (1 << 1 - 1)) != 0;



            bool[] dpad = { dpadRightBit, dpadLeftBit, dpadDownBit, dpadUpBit };
            byte c = 0;
            for (int i = 0; i < 4; ++i)
            {
                if (dpad[i])
                {
                    c |= (byte)(1 << i);
                }
            }

            bool dpadUp = false;
            bool dpadLeft = false;
            bool dpadDown = false;
            bool dpadRight = false;

            int dpad_state = c;
            switch (dpad_state)
            {
                case 0:
                    dpadUp = true;
                    dpadDown = false;
                    dpadLeft = false;
                    dpadRight = false;
                    break;
                case 1:
                    dpadUp = true;
                    dpadDown = false;
                    dpadLeft = false;
                    dpadRight = true;
                    break;
                case 2:
                    dpadUp = false;
                    dpadDown = false;
                    dpadLeft = false;
                    dpadRight = true;
                    break;
                case 3:
                    dpadUp = false;
                    dpadDown = true;
                    dpadLeft = false;
                    dpadRight = true;
                    break;
                case 4:
                    dpadUp = false;
                    dpadDown = true;
                    dpadLeft = false;
                    dpadRight = false;
                    break;
                case 5:
                    dpadUp = false;
                    dpadDown = true;
                    dpadLeft = true;
                    dpadRight = false;
                    break;
                case 6:
                    dpadUp = false;
                    dpadDown = false;
                    dpadLeft = true;
                    dpadRight = false;
                    break;
                case 7:
                    dpadUp = true;
                    dpadDown = false;
                    dpadLeft = true;
                    dpadRight = false;
                    break;
                case 8:
                    dpadUp = false;
                    dpadDown = false;
                    dpadLeft = false;
                    dpadRight = false;
                    break;

            }

            var thumbRight = ((byte)data[6] & (1 << 8 - 1)) != 0;
            var thumbLeft = ((byte)data[6] & (1 << 7 - 1)) != 0;
            var start = ((byte)data[6] & (1 << 6 - 1)) != 0;
            var options = ((byte)data[6] & (1 << 5 - 1)) != 0;
            var abit5 = ((byte)data[6] & (1 << 4 - 1)) != 0;
            var abit6 = ((byte)data[6] & (1 << 3 - 1)) != 0;
            var rb = ((byte)data[6] & (1 << 2 - 1)) != 0;
            var lb = ((byte)data[6] & (1 << 1 - 1)) != 0;

            bool[] r11 = { false, false, lb, rb, bitY, bitB, bitA, bitX };

            byte b11 = 0;
            for (int i = 0; i < 8; ++i)
            {
                if (r11[i])
                {
                    b11 |= (byte)(1 << i);
                }
            }
            Report[11] = b11;

            bool[] r10 = { options, thumbLeft, thumbRight, start, dpadUp, dpadRight, dpadDown, dpadLeft };
            byte b10 = 0;
            for (int i = 0; i < 8; ++i)
            {
                if (r10[i])
                {
                    b10 |= (byte)(1 << i);
                }
            }

            Report[10] = b10;

            //Guite
            var Guide = data[7] & (1 << 1 - 1);
            Report[12] = (byte)(Guide != 0 ? 0xFF : 0x00);

            return Report;
        }

        private void updateBatteryStatus(int status, bool isUsb)
        {
            int battery = 0;
            if (isUsb)
            {
                battery = (status - 16) * 10;
                if (battery > 100)
                    battery = 100;
            }
            else
            {
                battery = (status + 1) * 10;
                if (battery > 100)
                    battery = 100;
            }
            this.charge = (short) battery; // TODO short-circuit here when possible.  Need controller options broadcast on save.
            if (Global.getLedAsBatteryIndicator(deviceNum))
                LedColor = Global.getTransitionedColor(deviceNum, (uint)battery);
            else
                LedColor = Global.loadColor(deviceNum);

            if (Global.getFlashWhenLowBattery(deviceNum))
            {
                // FlashLed of 1 seems to be ignored, 2 is very fast -> 255 is very slow.
                switch (battery)
                {
                    case 10:
                        FlashLed = 15;
                        break;
                    case 20:
                        FlashLed = 31;
                        break;
                    case 30:
                        FlashLed = 63;
                        break;
                    case 40:
                        FlashLed = 127;
                        break;
                    case 50:
                        FlashLed = 255;
                        break;
                    default:
                        FlashLed = 0;
                        break;
                }
            }
            else
            {
                FlashLed = 0;
            }
        }

        private struct PreviousOutputState
        {
            public byte BigRumble, SmallRumble;
            public ledColor LedColor;
            public byte FlashLed;
        };
        PreviousOutputState previousOutputState;
        private byte[] outputData;

        public void sendOutputReport(bool always = false)
        {
            if (BigRumble == previousOutputState.BigRumble &&
                SmallRumble == previousOutputState.SmallRumble &&
                LedColor.red == previousOutputState.LedColor.red &&
                LedColor.green == previousOutputState.LedColor.green &&
                LedColor.blue == previousOutputState.LedColor.blue &&
                FlashLed == previousOutputState.FlashLed)
            {
                if (!always)
                    return;
            }

            for (int i = 0; i < outputData.Length; i++)
                outputData[i] = 0x0;

            if (!isUSB)
            {
                outputData[0] = 0x11;
                outputData[1] = 128;
                outputData[3] = 0xff;

                outputData[6] = BigRumble; //motor
                outputData[7] = SmallRumble; //motor

                outputData[8] = LedColor.red; //red
                outputData[9] = LedColor.green; //green
                outputData[10] = LedColor.blue; //blue
                outputData[11] = FlashLed; //flash;
                outputData[12] = FlashLed; //flash;
            }
            else
            {
                outputData[0] = 0x5;
                outputData[1] = 0xFF;
                outputData[4] = BigRumble; //motor
                outputData[5] = SmallRumble; //motor

                outputData[6] = LedColor.red; //red
                outputData[7] = LedColor.green; //green
                outputData[8] = LedColor.blue; //blue
                outputData[9] = FlashLed; //flash;
                outputData[10] = FlashLed; //flash;
            }

            if (isUSB ? Device.WriteOutputReportViaInterrupt(outputData) : Device.WriteOutputReportViaControl(outputData))
            {
                hid_device.Active = true;
                previousOutputState.BigRumble = BigRumble;
                previousOutputState.SmallRumble = SmallRumble;
                previousOutputState.LedColor.red = LedColor.red;
                previousOutputState.LedColor.green = LedColor.green;
                previousOutputState.LedColor.blue = LedColor.blue;
                previousOutputState.FlashLed = FlashLed;
            }
        }

        internal enum TouchpadMode { MouseWithButtons, MouseWithOneButton, MousePointerOnly, Disabled } // toggled with finger down on touchpad + PS button pushed
        internal TouchpadMode touchpadMode = TouchpadMode.MouseWithButtons;
        internal void nextTouchpadMode(BusDevice bd)
        {
            const int buzzDuration = 300, silenceDuration = 200; // we're not enforcing real-time... output reports are tied to input data packet processing currently
            switch (touchpadMode)
            {
                case TouchpadMode.MouseWithButtons:
                    touchpadMode = TouchpadMode.MouseWithOneButton;
                    bd.PerformHapticFeedback(() =>
                    {
                        bd.HapticBigRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticBigRumble = 0;
                        Thread.Sleep(silenceDuration);
                        bd.HapticBigRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticBigRumble = 0;
                        Thread.Sleep(silenceDuration);
                    });
                    break;
                case TouchpadMode.MouseWithOneButton:
                    touchpadMode = TouchpadMode.MousePointerOnly;
                    bd.PerformHapticFeedback(() =>
                    {
                        bd.HapticSmallRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticSmallRumble = 0;
                        Thread.Sleep(silenceDuration);
                        bd.HapticSmallRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticSmallRumble = 0;
                        Thread.Sleep(silenceDuration);
                    });
                    break;
                case TouchpadMode.MousePointerOnly:
                    touchpadMode = TouchpadMode.Disabled;
                    bd.PerformHapticFeedback(() =>
                    {
                        bd.HapticBigRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticBigRumble = 0;
                        Thread.Sleep(silenceDuration);
                        bd.HapticSmallRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticSmallRumble = 0;
                        Thread.Sleep(silenceDuration);
                    });
                    break;
                case TouchpadMode.Disabled:
                    touchpadMode = TouchpadMode.MouseWithButtons;
                    bd.PerformHapticFeedback(() =>
                    {
                        bd.HapticSmallRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticSmallRumble = 0;
                        Thread.Sleep(silenceDuration);
                        bd.HapticBigRumble = 255;
                        Thread.Sleep(buzzDuration);
                        bd.HapticBigRumble = 0;
                        Thread.Sleep(silenceDuration);
                    });
                    break;
            }
            bd.LogDebug("Controller " + (deviceNum + 1) + " touchpad mode: " + touchpadMode);
        }
        internal const int TOUCHPAD_DATA_OFFSET = 35;
        internal int lastTouchPadX, lastTouchPadY;
        internal bool lastTouchPadIsDown;
        internal bool lastMouseLeftClicked, lastMouseRightClicked, lastMouseMiddleClicked, lastPsChordHeld;
        internal BusDevice.ButtonDuringTouch[] psChordButton = { BusDevice.ButtonDuringTouch.PS };
        internal BusDevice.ButtonDuringTouch[] mouseLeftClickButtons = { BusDevice.ButtonDuringTouch.UpLeft, BusDevice.ButtonDuringTouch.Left, BusDevice.ButtonDuringTouch.DownLeft,
                                                                           BusDevice.ButtonDuringTouch.L1,
                                                                           BusDevice.ButtonDuringTouch.Square, BusDevice.ButtonDuringTouch.Share };
        internal BusDevice.ButtonDuringTouch[] mouseRightClickButtons = { BusDevice.ButtonDuringTouch.UpRight, BusDevice.ButtonDuringTouch.Right, BusDevice.ButtonDuringTouch.DownRight,
                                                                            BusDevice.ButtonDuringTouch.R1,
                                                                            BusDevice.ButtonDuringTouch.Circle, BusDevice.ButtonDuringTouch.Options };
        internal BusDevice.ButtonDuringTouch[] mouseMiddleClickButtons = { BusDevice.ButtonDuringTouch.UpLeft, BusDevice.ButtonDuringTouch.Up, BusDevice.ButtonDuringTouch.UpRight,
                                                                             BusDevice.ButtonDuringTouch.DownLeft, BusDevice.ButtonDuringTouch.Down, BusDevice.ButtonDuringTouch.DownRight,
                                                                             BusDevice.ButtonDuringTouch.Triangle, BusDevice.ButtonDuringTouch.Cross };
        internal INPUT[] sendInputs = new INPUT[1]; // will allow for keyboard + mouse/tablet input within one SendInput call
        private void handleTouchpad(byte[] data, bool touchPadIsDown, BusDevice dev)
        {
            int mouseDeltaX = 0, mouseDeltaY = 0,
                mouseDeltaLeftClick = 0, mouseDeltaRightClick = 0, mouseDeltaMiddleClick = 0, psButtonChordDelta = 0;

            bool _isActive = (data[0 + TOUCHPAD_DATA_OFFSET] >> 7) != 0 ? false : true; // finger(s) down
            if (_isActive)
            {
                //dev.LogDebug(data);
                int currentX = data[1 + TOUCHPAD_DATA_OFFSET] + ((data[2 + TOUCHPAD_DATA_OFFSET] & 0xF) * 255);
                int currentY = ((data[2 + TOUCHPAD_DATA_OFFSET] & 0xF0) >> 4) + (data[3 + TOUCHPAD_DATA_OFFSET] * 16);

                if (lastIsActive && touchpadMode != TouchpadMode.Disabled)
                {
                    double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0;
                    mouseDeltaX = (int)(sensitivity * (currentX - lastTouchPadX));
                    mouseDeltaY = (int)(sensitivity * (currentY - lastTouchPadY));
                }

                if (lastIsActive)
                {
                    bool touchpadAbsorbsButtons = true;
                    bool psChordHeld = dev.AbsorbButtonState(data, psChordButton, touchpadAbsorbsButtons);
                    if (psChordHeld != lastPsChordHeld)
                        psButtonChordDelta = psChordHeld ? 1 : -1;
                    lastPsChordHeld = psChordHeld;
                    touchpadAbsorbsButtons = touchpadMode == TouchpadMode.MouseWithButtons;
                    bool leftClicked = touchpadMode != TouchpadMode.Disabled && touchpadMode != TouchpadMode.MousePointerOnly && touchPadIsDown;
                    if (!leftClicked && touchpadMode == TouchpadMode.MouseWithButtons)
                        leftClicked = dev.AbsorbButtonState(data, mouseLeftClickButtons, touchpadAbsorbsButtons);
                    if (leftClicked != lastMouseLeftClicked)
                        mouseDeltaLeftClick = leftClicked ? 1 : -1;
                    lastMouseLeftClicked = leftClicked;
                    bool rightClicked = touchpadMode == TouchpadMode.MouseWithButtons && dev.AbsorbButtonState(data, mouseRightClickButtons, touchpadAbsorbsButtons);
                    if (rightClicked != lastMouseRightClicked)
                        mouseDeltaRightClick = rightClicked ? 1 : -1;
                    lastMouseRightClicked = rightClicked;
                    bool middleClicked = touchpadMode == TouchpadMode.MouseWithButtons && dev.AbsorbButtonState(data, mouseMiddleClickButtons, touchpadAbsorbsButtons);
                    if (middleClicked != lastMouseMiddleClicked)
                        mouseDeltaMiddleClick = middleClicked ? 1 : -1;
                    lastMouseMiddleClicked = middleClicked;
                }

                lastTouchPadX = currentX;
                lastTouchPadY = currentY;
                lastTouchPadIsDown = touchPadIsDown;
            }
            else // finger(s) lifted from touchpad while virtual mouse button(s) clicked
            {
                if (!lastIsActive) // neither active before or now
                    return;
                
                if (lastPsChordHeld)
                {
                    lastPsChordHeld = false;
                    psButtonChordDelta = -1;
                }
                if (lastMouseLeftClicked)
                {
                    lastMouseLeftClicked = false;
                    mouseDeltaLeftClick = -1;
                }
                if (lastMouseRightClicked)
                {
                    lastMouseRightClicked = false;
                    mouseDeltaRightClick = -1;
                }
                if (lastMouseMiddleClicked)
                {
                    lastMouseMiddleClicked = false;
                    mouseDeltaMiddleClick = -1;
                }
            }
            lastIsActive = _isActive;
            uint inputs = 0;
            if (_isActive && psButtonChordDelta != 0)
            {
                if (psButtonChordDelta == -1) // upon lift, perform mode switch
                {
                    nextTouchpadMode(dev);
                    // Also revert any buttons/keys/modifiers upon switching touchpad modes and don't take them on during the transition.
                    // Any held buttons or triggers are ignored until pushed in again, in the next touchpad mode.
                    if (lastMouseLeftClicked)
                    {
                        lastMouseLeftClicked = false;
                        mouseDeltaLeftClick = -1;
                    }
                    if (lastMouseRightClicked)
                    {
                        lastMouseRightClicked = false;
                        mouseDeltaRightClick = -1;
                    }
                    if (lastMouseMiddleClicked)
                    {
                        lastMouseMiddleClicked = false;
                        mouseDeltaMiddleClick = -1;
                    }
                }
                /* XXX this doesn't work yet, might need to use unicode, scancodes, or other keyboard input API
                uint i = inputs++;
                const ushort VK_MENU = 0x12, VK_TAB = 0x09;
                sendInputs[i].Type = INPUT_KEYBOARD;
                sendInputs[i].Data.Keyboard.Scan = 0; // left blank
                sendInputs[i].Data.Keyboard.Flags = psButtonChordDelta == -1 ? KEYEVENTF_KEYUP : 0;
                sendInputs[i].Data.Keyboard.Time = 0;
                sendInputs[i].Data.Keyboard.ExtraInfo = IntPtr.Zero;
                sendInputs[i].Data.Keyboard.Vk = VK_MENU;
                sendInputs[i = inputs++] = sendInputs[i - 1];
                sendInputs[i].Data.Keyboard.Vk = VK_TAB; */
            }
            if (mouseDeltaX == 0 && mouseDeltaY == 0 && mouseDeltaLeftClick == 0 && mouseDeltaRightClick == 0 && mouseDeltaMiddleClick == 0)
            {
                // no mouse input events to report
            }
            else if (mouseDeltaLeftClick == 0 && mouseDeltaRightClick == 0 && mouseDeltaMiddleClick == 0) // only move cursor
            {
                uint i = inputs++;
                sendInputs[i].Type = INPUT_MOUSE;
                sendInputs[i].Data.Mouse.ExtraInfo = IntPtr.Zero;
                sendInputs[i].Data.Mouse.Flags = MOUSEEVENTF_MOVE;
                sendInputs[i].Data.Mouse.MouseData = 0;
                sendInputs[i].Data.Mouse.Time = 0;
                sendInputs[i].Data.Mouse.X = mouseDeltaX;
                sendInputs[i].Data.Mouse.Y = mouseDeltaY;
            }
            else if (mouseDeltaX == 0 && mouseDeltaY == 0) // only change click state
            {
                uint i = inputs++;
                sendInputs[i].Type = INPUT_MOUSE;
                sendInputs[i].Data.Mouse.ExtraInfo = IntPtr.Zero;
                sendInputs[i].Data.Mouse.Flags = 0;
                if (mouseDeltaLeftClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaLeftClick == 1 ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
                if (mouseDeltaRightClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaRightClick == 1 ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
                if (mouseDeltaMiddleClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaMiddleClick == 1 ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
                sendInputs[i].Data.Mouse.MouseData = 0;
                sendInputs[i].Data.Mouse.Time = 0;
                sendInputs[i].Data.Mouse.X = 0;
                sendInputs[i].Data.Mouse.Y = 0;
            }
            else if (touchpadMode != TouchpadMode.Disabled) // move -> change click state (order shouldn't really matter due to real-life latency)
            {
                uint i = inputs++;
                sendInputs[i].Type = INPUT_MOUSE;
                sendInputs[i].Data.Mouse.ExtraInfo = IntPtr.Zero;
                sendInputs[i].Data.Mouse.Flags = MOUSEEVENTF_MOVE;
                if (mouseDeltaLeftClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaLeftClick == 1 ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
                if (mouseDeltaRightClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaRightClick == 1 ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
                if (mouseDeltaMiddleClick != 0)
                    sendInputs[i].Data.Mouse.Flags |= mouseDeltaMiddleClick == 1 ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
                sendInputs[i].Data.Mouse.MouseData = 0;
                sendInputs[i].Data.Mouse.Time = 0;
                sendInputs[i].Data.Mouse.X = mouseDeltaX;
                sendInputs[i].Data.Mouse.Y = mouseDeltaY;
            }
            if (inputs > 0)
            {
                uint result = SendInput(inputs, sendInputs, Marshal.SizeOf(sendInputs[0]) * (int)inputs);
            }
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct INPUT
        {
            public uint Type;
            public MOUSEKEYBDHARDWAREINPUT Data;
        }
        internal const uint INPUT_MOUSE = 0, INPUT_KEYBOARD = 1, INPUT_HARDWARE = 2;

        /// <summary>
        /// http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/f0e82d6e-4999-4d22-b3d3-32b25f61fb2a
        /// </summary>
        [StructLayout(LayoutKind.Explicit)]
        internal struct MOUSEKEYBDHARDWAREINPUT
        {
            [FieldOffset(0)]
            public HARDWAREINPUT Hardware;
            [FieldOffset(0)]
            public KEYBDINPUT Keyboard;
            [FieldOffset(0)]
            public MOUSEINPUT Mouse;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct HARDWAREINPUT
        {
            public uint Msg;
            public ushort ParamL;
            public ushort ParamH;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct KEYBDINPUT
        {
            public ushort Vk;
            public ushort Scan;
            public uint Flags;
            public uint Time;
            public IntPtr ExtraInfo;
        }

        /// <summary>
        /// http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/2abc6be8-c593-4686-93d2-89785232dacd
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct MOUSEINPUT
        {
            public int X;
            public int Y;
            public uint MouseData;
            public uint Flags;
            public uint Time;
            public IntPtr ExtraInfo;
        }
        internal const uint MOUSEEVENTF_MOVE = 1, // just apply X/Y (delta due to not setting absolute flag)
            MOUSEEVENTF_LEFTDOWN = 2, MOUSEEVENTF_LEFTUP = 4,
            MOUSEEVENTF_RIGHTDOWN = 8, MOUSEEVENTF_RIGHTUP = 16,
            MOUSEEVENTF_MIDDLEDOWN = 32, MOUSEEVENTF_MIDDLEUP = 64,
            KEYEVENTF_KEYUP = 2;

        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputs);

        public String toString()
        {
            return "Controller " + (deviceNum + 1) + ": " + "Battery = "+charge+"%," +" Touchpad Enabled = " + isTouchEnabled + (isUSB ? " (USB)" : " (BT)");
        }

    }
    

 

}
